﻿using MyHome.Server.PluginBase;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;

namespace MyHome.Server.DataAccess
{
    public class Repository
    {
        private MyHomeEntities context;

        public IDictionary<string, Type> PluginTypes { get; set; }

        public IDictionary<string, string> Configuration { get; private set; }

        public Repository()
        {
            context = new MyHomeEntities();
            var configuration = new Swordfish.NET.Collections.ConcurrentObservableDictionary<string, string>(context.Configurations.ToDictionary(pair => pair.Key, pair => pair.Value));
            configuration.CollectionChanged += configuration_CollectionChanged;
            Configuration = configuration;
        }

        void configuration_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
                    foreach (KeyValuePair<string, string> item in e.NewItems)
                    {
                        context.Configurations.Add(new DataAccess.Configuration { Key = item.Key, Value = item.Value });
                    }
                    context.SaveChanges();
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Move:
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:
                    foreach (KeyValuePair<string, string> item in e.OldItems)
                    {
                        context.Configurations.Remove(new DataAccess.Configuration { Key = item.Key, Value = item.Value });
                    }
                    context.SaveChanges();
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Replace:
                    foreach (KeyValuePair<string, string> item in e.NewItems)
                    {
                        context.Configurations.Add(new DataAccess.Configuration { Key = item.Key, Value = item.Value });
                    }
                    foreach (KeyValuePair<string, string> item in e.OldItems)
                    {
                        context.Configurations.Remove(new DataAccess.Configuration { Key = item.Key, Value = item.Value });
                    }
                    context.SaveChanges();
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Reset:
                    var all = context.Configurations.ToList();
                    context.Configurations.RemoveRange(all);
                    break;
                default:
                    break;
            }
        }

        #region Plugin

        public IEnumerable<IPlugin> GetPlugins()
        {
            return context.Plugins.ToList().Select(p => Deserialize(p));
        }

        public IPlugin GetPlugin(int id)
        {
            var entityPlugin = context.Plugins.Find(id);
            return Deserialize(entityPlugin);
        }

        public IPlugin AddPlugin(IPlugin plugin)
        {
            if (context.Plugins.Find(plugin.Id) != null)
                throw new ArgumentException(string.Format("The plugin with the id {0} already exists in the database.", plugin.Id));

            var entityPlugin = new Plugin { PluginType = plugin.GetType().FullName, PluginValue = "" };
            context.Plugins.Add(entityPlugin);
            context.SaveChanges();

            plugin.Id = entityPlugin.Id;

            var serialized = Serialize(plugin);
            entityPlugin.PluginValue = serialized;
            context.SaveChanges();

            return plugin;
        }

        public void UpdatePlugin(IPlugin plugin)
        {
            var entityPlugin = context.Plugins.Find(plugin.Id);
            if (entityPlugin == null)
                throw new ArgumentException(string.Format("The plugin with the id {0} does not exist in the database.", plugin.Id));

            var serialized = Serialize(plugin);
            entityPlugin.PluginType = plugin.GetType().FullName;
            entityPlugin.PluginValue = serialized;
            context.SaveChanges();
        }

        public void DeletePlugin(int id)
        {
            var entityPlugin = context.Plugins.Find(id);
            if (entityPlugin == null)
                throw new ArgumentException(string.Format("The plugin with the id {0} does not exist in the database.", id));

            context.Plugins.Remove(entityPlugin);
            context.SaveChanges();
        }

        #endregion Plugin

        #region PluginConnections

        public IEnumerable<IPluginConnection<object>> GetPluginConnections()
        {
            return context.PluginConnections.ToList().Select(p => new PluginConnection<object> { Id = p.Id, InputId = p.Input, OutputId = p.Output });
        }

        public IPluginConnection<object> GetPluginConnection(int id)
        {
            var p = context.PluginConnections.Find(id);
            if (p == null)
                return null;

            return new PluginConnection<object> { Id = p.Id, InputId = p.Input, OutputId = p.Output };
        }

        public IPluginConnection<T> AddPluginConnection<T>(IPluginConnection<T> pluginConnection)
        {
            if (context.PluginConnections.Find(pluginConnection.Id) != null)
                throw new ArgumentException(string.Format("The PluginConnection with the id {0} already exists in the database.", pluginConnection.Id));

            var entityPluginConnection = new PluginConnection { Input = pluginConnection.InputId, Output = pluginConnection.OutputId };
            context.PluginConnections.Add(entityPluginConnection);
            context.SaveChanges();
            pluginConnection.Id = entityPluginConnection.Id;
            return pluginConnection;
        }

        public void UpdatePluginConnection<T>(IPluginConnection<T> pluginConnection)
        {
            var entityPluginConnection = context.PluginConnections.Find(pluginConnection.Id);
            if (entityPluginConnection == null)
                throw new ArgumentException(string.Format("The PluginConnection with the id {0} does not exist in the database.", pluginConnection.Id));

            entityPluginConnection.Input = pluginConnection.InputId;
            entityPluginConnection.Output = pluginConnection.OutputId;
            context.SaveChanges();
        }

        public void DeletePluginConnection(int id)
        {
            var entityPluginConnection = context.PluginConnections.Find(id);
            if (entityPluginConnection == null)
                throw new ArgumentException(string.Format("The PluginConnection with the id {0} does not exist in the database.", id));

            context.PluginConnections.Remove(entityPluginConnection);
            context.SaveChanges();
        }

        #endregion PluginConnections

        #region Serialization

        private IPlugin Deserialize(Plugin entityPlugin)
        {
            if (entityPlugin == null)
                return null;

            return Deserialize(entityPlugin.PluginValue, PluginTypes[entityPlugin.PluginType]);
        }

        private static IPlugin Deserialize(string serializedPlugin, Type pluginType)
        {
            return (IPlugin)JsonConvert.DeserializeObject(serializedPlugin, pluginType);
        }

        private static string Serialize(IPlugin plugin)
        {
            return JsonConvert.SerializeObject(plugin);
        }

        #endregion Serialization
    }
}